// Mustache.js inspired template engine implemented in TypeScript

/**
 * 定义模板引擎支持的标签类型
 */
export type TagType = 'text' | 'variable' | 'unescaped' | 'section' | 'inverse' | 'partial' | 'comment' | 'close';

/**
 * 模板解析后生成的令牌接口
 */
export interface Token {
  type: TagType;
  value: string;
  children?: Token[];
  contextParam?: string;
}

/**
 * 模板引擎配置选项
 */
export interface TemplateEngineOptions {
  tags?: [string, string];
  autoEscape?: boolean;
  debug?: boolean;
  preserveWhitespace?: boolean;
}

/**
 * 模板引擎类
 */
export class TemplateEngine {
  private readonly openingTag: string;
  private readonly closingTag: string;
  private readonly autoEscape: boolean;
  private readonly debug: boolean;
  private readonly preserveWhitespace: boolean;

  /**
   * 创建模板引擎实例
   * @param options 配置选项
   */
  constructor(options: TemplateEngineOptions = {}) {
    const tags = options.tags || ['{{', '}}'];
    this.openingTag = tags[0];
    this.closingTag = tags[1];
    this.autoEscape = options.autoEscape !== false; // 默认启用HTML转义
    this.debug = options.debug || false; // 默认禁用调试日志
    this.preserveWhitespace = options.preserveWhitespace || false; // 默认不保留空白
  }

  /**
   * 转义正则表达式特殊字符
   * @param str 要转义的字符串
   * @returns 转义后的字符串
   */
  private escapeRegExp(str: string): string {
    return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  }

  /**
   * HTML转义
   * @param str 要转义的字符串
   * @returns 转义后的字符串
   */
  private escapeHtml(str: string): string {
    const entityMap: Record<string, string> = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;',
      '/': '/',  // 不转义斜杠，符合测试预期
      '`': '&#x60;',
      '=': '&#x3D;'
    };
    return str.replace(/[&<>"'`=\/]/g, s => entityMap[s] || s);
  }

  /**
   * 解析模板字符串为令牌数组
   * @param template 模板字符串
   * @returns 解析后的令牌数组
   */
  parse(template: string): Token[] {
    if (this.debug) console.log('Parsing template:', template);
    
    const tokens: Token[] = [];
    const tagRegex = new RegExp(
      `${this.escapeRegExp(this.openingTag)}\\s*(\\/|#|\\^|>|&|!)?\\s*([^}]*?)\\s*${this.escapeRegExp(this.closingTag)}`,
      'g'
    );
    
    let lastIndex = 0;
    let match: RegExpExecArray | null;
    
    while ((match = tagRegex.exec(template)) !== null) {
      const [fullMatch, tagType, tagValue] = match;
      const startIndex = match.index;
      const endIndex = startIndex + fullMatch.length;
      
      // 添加标签前的文本
      if (startIndex > lastIndex) {
        let text = template.slice(lastIndex, startIndex);
        
        // 如果不保留空白，压缩多个空白字符为一个空格
        if (!this.preserveWhitespace) {
          text = text.replace(/\s+/g, ' ');
        }
        
        tokens.push({
          type: 'text',
          value: text
        });
      }
      
      // 确定标签类型
      let type: TagType = 'variable';
      let value = tagValue.trim();
      let contextParam: string | undefined;
      
      switch (tagType) {
        case '#':
          type = 'section';
          break;
        case '^':
          type = 'inverse';
          break;
        case '/':
          type = 'close';
          break;
        case '>':
          type = 'partial';
          // 分割部分模板名称和上下文参数
          const parts = value.split(/\s+/, 2);
          if (parts.length > 1) {
            value = parts[0];
            contextParam = parts[1];
          }
          break;
        case '&':
          type = 'unescaped';
          break;
        case '!':
          type = 'comment';
          break;
        default:
          type = 'variable';
      }
      
      // 忽略注释
      if (type !== 'comment') {
        tokens.push({ 
          type, 
          value,
          contextParam
        });
      }
      
      lastIndex = endIndex;
    }
    
    // 添加剩余的文本
    if (lastIndex < template.length) {
      let text = template.slice(lastIndex);
      
      // 如果不保留空白，压缩多个空白字符为一个空格
      if (!this.preserveWhitespace) {
        text = text.replace(/\s+/g, ' ');
      }
      
      tokens.push({
        type: 'text',
        value: text
      });
    }
    
    return this.nestTokens(tokens);
  }
  
  /**
   * 将令牌嵌套为树形结构
   * @param tokens 扁平的令牌数组
   * @returns 嵌套的令牌数组
   */
  private nestTokens(tokens: Token[]): Token[] {
    const nestedTokens: Token[] = [];
    const sections: Token[] = [];
    let collector = nestedTokens;
    
    for (let i = 0; i < tokens.length; i++) {
      const token = tokens[i];
      
      if (token.type === 'section' || token.type === 'inverse') {
        token.children = [];
        collector.push(token);
        sections.push(token);
        collector = token.children;
      } else if (token.type === 'close') {
        if (sections.length === 0) {
          throw new Error(`Unexpected closing tag: ${token.value}`);
        }
        
        const section = sections.pop()!;
        if (section.value !== token.value) {
          throw new Error(`Mismatched closing tag: expected ${section.value}, got ${token.value}`);
        }
        
        collector = sections.length > 0 
          ? sections[sections.length - 1].children! 
          : nestedTokens;
      } else {
        collector.push(token);
      }
    }
    
    if (sections.length > 0) {
      const unclosed = sections.map(s => s.value).join(', ');
      throw new Error(`Unclosed sections: ${unclosed}`);
    }
    
    return nestedTokens;
  }

  /**
   * 在上下文中查找值
   * @param context 上下文栈
   * @param name 要查找的名称
   * @returns 找到的值
   */
  private lookup(context: any[], name: string): any {
    // 处理父级上下文访问 (../name)
    if (name.startsWith('../')) {
      const parentName = name.substring(3);
      if (context.length > 1) {
        // 从倒数第二个上下文开始查找
        for (let i = context.length - 2; i >= 0; i--) {
          const ctx = context[i];
          if (ctx && typeof ctx === 'object') {
            const value = this.lookupValue(ctx, parentName);
            if (value !== undefined) {
              return value;
            }
          }
        }
      }
      return undefined;
    }
    
    // 处理当前上下文 (.)
    if (name === '.') {
      return context[context.length - 1];
    }
    
    // 处理普通变量，支持嵌套属性访问（如 user.name）
    for (let i = context.length - 1; i >= 0; i--) {
      const ctx = context[i];
      if (ctx && typeof ctx === 'object') {
        const value = this.lookupValue(ctx, name);
        if (value !== undefined) {
          return value;
        }
      }
    }
    
    return undefined;
  }
  
  /**
   * 在对象中查找值，支持嵌套属性访问（如 user.name）
   * @param obj 要查找的对象
   * @param path 属性路径
   * @returns 找到的值
   */
  private lookupValue(obj: any, path: string): any {
    // 如果路径中包含点，则需要递归查找
    if (path.includes('.')) {
      const parts = path.split('.');
      let current = obj;
      
      for (const part of parts) {
        if (current === null || current === undefined || typeof current !== 'object') {
          return undefined;
        }
        
        current = current[part];
      }
      
      // 如果值是函数，执行它
      if (typeof current === 'function') {
        return current.call(obj);
      }
      
      return current;
    } else {
      // 直接属性访问
      if (path in obj) {
        const value = obj[path];
        
        // 如果值是函数，执行它
        if (typeof value === 'function') {
          return value.call(obj);
        }
        
        return value;
      }
    }
    
    return undefined;
  }

  /**
   * 检查值是否为真值
   * @param value 要检查的值
   * @returns 是否为真值
   */
  private isTruthy(value: any): boolean {
    if (value === false || value === null || value === undefined) {
      return false;
    }
    
    if (Array.isArray(value) && value.length === 0) {
      return false;
    }
    
    return true;
  }

  /**
   * 渲染模板
   * @param template 模板字符串
   * @param context 数据上下文
   * @param partials 部分模板
   * @returns 渲染后的字符串
   */
  render(
    template: string,
    context: Record<string, any> = {},
    partials: Record<string, string> = {}
  ): string {
    const tokens = this.parse(template);
    const result = this.renderTokens(tokens, [context], partials);
    console.log(result);
    return result;
  }

  /**
   * 渲染令牌为最终输出
   * @param tokens 解析后的令牌数组
   * @param context 上下文栈
   * @param partials 部分模板映射
   * @returns 渲染后的字符串
   */
  private renderTokens(
    tokens: Token[],
    context: any[],
    partials: Record<string, string>
  ): string {
    let result = '';
    
    for (const token of tokens) {
      switch (token.type) {
        case 'text':
          result += token.value;
          break;
          
        case 'variable': {
          const value = this.lookup(context, token.value);
          if (value !== undefined) {
            const valueStr = String(value);
            result += this.autoEscape ? this.escapeHtml(valueStr) : valueStr;
          }
          break;
        }
        
        case 'unescaped': {
          const value = this.lookup(context, token.value);
          if (value !== undefined) {
            result += String(value);
          }
          break;
        }
        
        case 'section': {
          const value = this.lookup(context, token.value);
          
          if (this.isTruthy(value)) {
            if (Array.isArray(value)) {
              // 数组迭代
              for (const item of value) {
                result += this.renderTokens(token.children || [], [...context, item], partials);
              }
            } else if (typeof value === 'object' && value !== null) {
              // 对象上下文
              result += this.renderTokens(token.children || [], [...context, value], partials);
            } else {
              // 其他真值
              result += this.renderTokens(token.children || [], context, partials);
            }
          }
          break;
        }
        
        case 'inverse': {
          const value = this.lookup(context, token.value);
          
          if (!this.isTruthy(value)) {
            result += this.renderTokens(token.children || [], context, partials);
          }
          break;
        }
        
        case 'partial': {
          const partialTemplate = partials[token.value];
          if (partialTemplate) {
            // 处理上下文参数
            let partialContext = context;
            if (token.contextParam) {
              const contextValue = this.lookup(context, token.contextParam);
              if (contextValue !== undefined) {
                partialContext = [...context, contextValue];
              }
            }
            
            const partialTokens = this.parse(partialTemplate);
            result += this.renderTokens(partialTokens, partialContext, partials);
          }
          break;
        }
      }
    }
    
    return result;
  }
}

// 默认导出实例
export default new TemplateEngine();